Skip to content

Conversation

@CromNEXT
Copy link
Contributor

@CromNEXT CromNEXT commented Jan 28, 2026

개요

PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

🧩 작업 내용

  • 회원가입, 로그인 기능 구현(JWT 기반)
  • Id가 String값이면 에러가 발생해서 Long으로 수정
  • 커스텀 Status추가(에러/성공 발생 시 Enum에 추가해서 전달하도록 통일)
  • swagger문서화는 컨트롤러에서 docs를 구현하도록 작성

📸 스크린샷(선택)

image image

📣 To Reviewers

  • Reviewers : 팀 선택
  • Labels : 작업 유형, 자기 자신

Summary by CodeRabbit

  • New Features

    • 회원가입·로그인·토큰 재발급 REST API 및 JWT 기반 인증 흐름 추가, 표준 응답형식(BaseResponse) 도입
  • Behavior / UX

    • 전역 예외 처리로 검증·오류 응답 일관화 및 한국어 친화적 메시지 제공
  • Security

    • 인증 범위 강화, JWT 필터·토큰 발급/검증·비밀번호 암호화 지원, Spring Security 통합
  • Data Model

    • 주요 엔티티 식별자 String→Long 변경, 사용자에 비밀번호 필드 추가
  • Docs

    • OpenAPI/Swagger UI 업그레이드 및 인증 API 문서화 보강

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Warning

Rate limit exceeded

@CromNEXT has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Spring Security 및 JJWT 의존성 추가, JPA Auditing 활성화. 공통 응답/상태·전역 예외 처리 추가. JWT 제공자·필터·UserDetailsService·보안 설정 및 인증 API/서비스/DTO/리포지토리 추가. 다수 엔티티의 PK 타입 String→Long 변경 및 Users에 password 칼럼 추가.

Changes

Cohort / File(s) Summary
빌드·앱 설정
build.gradle, src/main/java/ssurent/ssurentbe/SsurentbeApplication.java
Spring Security, Springdoc OpenAPI 버전 업데이트, JJWT 의존성 추가 및 @EnableJpaAuditing 적용.
공통 응답·상태
src/main/java/ssurent/ssurentbe/common/base/BaseResponse.java, src/main/java/ssurent/ssurentbe/common/status/SuccessStatus.java, src/main/java/ssurent/ssurentbe/common/status/ErrorStatus.java
제네릭 API 응답 타입 추가 및 성공/오류 상태 enum(HTTP 상태·코드·메시지) 추가.
예외 처리
src/main/java/ssurent/ssurentbe/common/exception/GeneralException.java, src/main/java/ssurent/ssurentbe/common/exception/GlobalExceptionHandler.java
GeneralException 도입 및 전역 예외 처리(GeneralException, IllegalArgumentException, NullPointerException, validation 등) 구현.
보안 구성·필터·토큰 제공자
src/main/java/ssurent/ssurentbe/common/config/SecurityConfig.java, src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java, src/main/java/ssurent/ssurentbe/common/jwt/JwtTokenProvider.java, src/main/java/ssurent/ssurentbe/common/config/SwaggerConfig.java
SecurityFilterChain 구성(공개 엔드포인트 지정), JWT 필터 등록, JwtTokenProvider 구현(생성·검증·파싱·Authentication 변환 및 JWT 예외 매핑), Swagger에 Bearer 보안 스킴 추가.
사용자 인증·서비스·리포지토리
src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java, src/main/java/ssurent/ssurentbe/domain/users/repository/UserRepository.java, src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java
studentNum 기반 UserDetailsService, Users용 JpaRepository 추가, AuthService에 signup/login/refresh 로직(중복검사, 비밀번호 암호화, 토큰 발급/검증) 구현.
인증 컨트롤러·문서·DTO
src/main/java/ssurent/ssurentbe/domain/users/controller/AuthController.java, src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java, src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java, .../LoginRequest.java, .../TokenResponse.java, .../TokenResponseWrapper.java
/api/auth 엔드포인트(회원가입, 로그인, 리프레시) 컨트롤러 및 OpenAPI 문서 인터페이스, 요청/응답 DTO 추가.
도메인 엔티티 변경
src/main/java/ssurent/ssurentbe/domain/.../*
src/main/java/ssurent/ssurentbe/domain/users/entity/Users.java, .../Panalty.java (삭제), .../UserPanaltyLog.java, src/main/java/ssurent/ssurentbe/domain/assists/entity/Assists.java, src/main/java/ssurent/ssurentbe/domain/item/entity/Category.java, src/main/java/ssurent/ssurentbe/domain/item/entity/Items.java, src/main/java/ssurent/ssurentbe/domain/rental/entity/RentalHistory.java
여러 엔티티의 ID 타입 String → Long 변경, 일부 엔티티에서 isDeleteddeleted로 필드명 변경, Users에 non-null password 칼럼 추가, Panalty 엔티티 삭제 및 관련 연관 필드 제거, PanaltyTypes에서 BANNED 제거.
보안 유틸·빈
src/main/java/ssurent/ssurentbe/common/config/SecurityConfig.java
BCrypt PasswordEncoder 및 AuthenticationManager 빈 노출, JwtAuthenticationFilter 주입 및 필터 체인에 등록.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant AuthController as AuthController
    participant AuthService as AuthService
    participant UserRepository as UserRepository
    participant JwtTokenProvider as JwtTokenProvider

    rect rgba(220,100,100,0.5)
    Note over Client,AuthController: 로그인 흐름
    Client->>AuthController: POST /api/auth/login (studentNum,password)
    AuthController->>AuthService: login(request)
    AuthService->>UserRepository: findByStudentNum(studentNum)
    UserRepository-->>AuthService: Users
    AuthService->>AuthService: 비밀번호 검증 및 상태 체크
    AuthService->>JwtTokenProvider: createAccessToken(studentNum)
    JwtTokenProvider-->>AuthService: accessToken
    AuthService->>JwtTokenProvider: createRefreshToken(studentNum)
    JwtTokenProvider-->>AuthService: refreshToken
    AuthService-->>AuthController: TokenResponse
    AuthController-->>Client: BaseResponse<TokenResponse>
    end
Loading
sequenceDiagram
    actor Client
    participant Filter as JwtAuthenticationFilter
    participant JwtProvider as JwtTokenProvider
    participant UserDetailsService as CustomUserDetailsService
    participant SecurityContext as SecurityContext

    rect rgba(100,220,100,0.5)
    Note over Client,Filter: 요청 → JWT 검증 흐름
    Client->>Filter: 요청 (Authorization: Bearer {token})
    Filter->>JwtProvider: validateToken(token)
    JwtProvider-->>Filter: valid / throws
    Filter->>JwtProvider: getAuthentication(token)
    JwtProvider->>UserDetailsService: loadUserByUsername(studentNum)
    UserDetailsService-->>JwtProvider: UserDetails
    JwtProvider-->>Filter: Authentication
    Filter->>SecurityContext: setAuthentication(auth)
    Filter-->>Client: 다음 필터/컨트롤러로 전달
    end
Loading
sequenceDiagram
    actor Client
    participant AuthController as AuthController
    participant AuthService as AuthService
    participant JwtTokenProvider as JwtTokenProvider
    participant UserRepository as UserRepository

    rect rgba(100,100,220,0.5)
    Note over Client,AuthController: 리프레시 토큰 재발급 흐름
    Client->>AuthController: POST /api/auth/refresh (Authorization: Bearer {refreshToken})
    AuthController->>AuthService: refresh(refreshToken)
    AuthService->>JwtTokenProvider: validateToken(refreshToken)
    JwtTokenProvider-->>AuthService: valid
    AuthService->>JwtTokenProvider: isRefreshToken(refreshToken)
    JwtTokenProvider-->>AuthService: true/false
    AuthService->>JwtTokenProvider: getStudentNum(refreshToken)
    JwtTokenProvider-->>AuthService: studentNum
    AuthService->>UserRepository: findByStudentNum(studentNum)
    UserRepository-->>AuthService: Users
    AuthService->>JwtTokenProvider: createAccessToken / createRefreshToken
    JwtTokenProvider-->>AuthService: new tokens
    AuthService-->>AuthController: TokenResponse
    AuthController-->>Client: BaseResponse<TokenResponse>
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 토끼의 축하 한마디

비밀 키에 토큰을 굴려 길을 잇고,
가입하고 로그인하며 깡충깡충 뛰네.
갱신의 바람에 또다시 날아올라,
당근보다 달콤한 안전한 연결! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항인 기본 인증 기능 구현을 명확하게 설명하고 있으며, 간결하고 구체적입니다.
Description check ✅ Passed PR 설명이 대부분의 필수 항목(개요, PR 유형, 체크리스트, 작업 내용)을 포함하고 있으며 충분히 상세합니다.
Linked Issues check ✅ Passed 연결된 이슈 #3의 모든 목표(회원가입 [#3-1], 로그인 [#3-2], JWT 구현 [#3-3])가 PR의 코드 변경에 의해 충족되었습니다.
Out of Scope Changes check ✅ Passed 데이터베이스 식별자 타입 변경(String→Long)은 이슈 #3의 요구사항 달성에 필요한 범위 내 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CromNEXT CromNEXT requested review from Todom2 and fprtmjinho January 28, 2026 19:29
@CromNEXT CromNEXT self-assigned this Jan 28, 2026
@CromNEXT CromNEXT added the feature 새로운기능구현 label Jan 28, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/ssurent/ssurentbe/domain/users/entity/Users.java (1)

18-33: 스키마 마이그레이션/백필 없이는 운영 장애 가능성이 큽니다.
PK 타입 변경과 password NOT NULL 추가는 기존 데이터가 있는 환경에서 마이그레이션 실패나 런타임 오류를 유발할 수 있습니다. Flyway/Liquibase로 PK 타입 변경, 기존 사용자 비밀번호 백필(또는 단계적 nullable 전환) 계획을 명확히 해 주세요.

🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 49-55: Update the springdoc-openapi dependencies to the 3.0.x line
and add a Bouncy Castle override: replace the existing springdoc coordinates
(org.springdoc:springdoc-openapi-starter-webmvc-ui and
org.springdoc:springdoc-openapi-starter-webmvc-api currently at 2.8.13) with
3.0.0 (or latest 3.0.x) to be compatible with Spring Boot 4.0.x, and add an
explicit org.bouncycastle:bcpkix-jdk18on dependency at 1.79+ to override the
transitive Bouncy Castle pulled in by io.jsonwebtoken:jjwt-*
(jjwt-api/jjwt-impl/jjwt-jackson) and mitigate CVE-2025-8916.

In
`@src/main/java/ssurent/ssurentbe/common/exception/GlobalExceptionHandler.java`:
- Around line 12-17: The handler in
GlobalExceptionHandler.handleGeneralException currently casts e.getStatus() to
ErrorStatus which can throw ClassCastException at runtime; change the
implementation to avoid unsafe casting by checking e.getStatus() with instanceof
(or using a safe accessor) and handle non-ErrorStatus cases (e.g., map unknown
statuses to a default ErrorStatus or return a generic error response) before
calling BaseResponse.error; ensure you reference GeneralException.getStatus()
and ErrorStatus and produce a ResponseEntity with an appropriate HttpStatus for
fallback paths.

In `@src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java`:
- Around line 42-44: JwtAuthenticationFilter currently unsafely casts
GeneralException.getStatus() to ErrorStatus in the catch block; update the
handling so the cast is safe by either (A) changing GeneralException to only
accept ErrorStatus in its constructor (and update usages) or (B) adding a
runtime check in JwtAuthenticationFilter (and similarly in
GlobalExceptionHandler) that tests if e.getStatus() instanceof ErrorStatus
before casting—if true, call sendErrorResponse(response, (ErrorStatus)
e.getStatus()); otherwise map/convert the BaseStatus to a safe ErrorStatus (or
use a default/generic error response) and log the unexpected status type. Ensure
you update both JwtAuthenticationFilter and GlobalExceptionHandler to follow the
same safe pattern.

In
`@src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java`:
- Around line 22-31: loadUserByUsername currently only checks existence and
returns a UserDetails, allowing deleted/disabled accounts to authenticate;
update CustomUserDetailsService.loadUserByUsername to check the Users entity
flags (e.g., isDeleted() and getStatus()/status enum) after fetching from
userRepository and throw UsernameNotFoundException or a custom DisabledException
when the account is deleted or inactive. Ensure the exception uses a clear
ErrorStatus (or map to ErrorStatus.USER_NOT_FOUND or a new ErrorStatus for
disabled users) so getAuthentication() will reject such tokens, and keep
returning the Spring Security User with roles only for active users.

In `@src/main/java/ssurent/ssurentbe/domain/users/controller/AuthController.java`:
- Around line 41-43: AuthController.refresh currently unsafely extracts the
token with authorization.replace("Bearer ", "") which can NPE or return wrong
value; change it to the same safe parsing used by
JwtAuthenticationFilter.resolveToken — i.e., check for null/empty, verify the
header startsWith("Bearer "), then extract the substring after the prefix (or
call JwtAuthenticationFilter.resolveToken(authorization) if accessible) and
handle a missing/invalid token by returning an appropriate error (or throwing
the same exception path used elsewhere) before calling
authService.refresh(refreshToken).

In
`@src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java`:
- Around line 21-46: Create concrete wrapper response classes that extend
BaseResponse with the generic type parameter (e.g., class SignupResponse extends
BaseResponse<TokenResponse>, LoginResponse extends BaseResponse<TokenResponse>,
RefreshResponse extends BaseResponse<TokenResponse>) and update the
`@ApiResponse/`@Content annotations on the signup, login and refresh operations to
use `@Schema`(implementation = SignupResponse.class) / LoginResponse.class /
RefreshResponse.class respectively so springdoc-openapi can surface
TokenResponse fields (accessToken, refreshToken, tokenType) in Swagger; locate
the annotations around the methods named signup, login and the token refresh
operation and replace the existing `@Schema`(implementation = BaseResponse.class)
references with the new concrete classes.

In `@src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java`:
- Around line 3-8: SignupRequest currently uses the record-generated toString
which exposes sensitive fields; override SignupRequest#toString to return a safe
representation that masks phoneNum and password (e.g., show only last 2-4
digits/characters or replace with fixed ********), keep non-sensitive fields
like name/studentNum readable if desired, and ensure the method is implemented
on the record itself so any logging/exception handling uses the masked output.

In `@src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java`:
- Around line 69-72: The if-check in AuthService.refresh around
jwtTokenProvider.validateToken(refreshToken) is dead because validateToken
throws GeneralException on invalid tokens; remove the if-block and either (a)
simply call jwtTokenProvider.validateToken(refreshToken) and let its exception
propagate, or (b) wrap the call in a try-catch that catches the thrown exception
and rethrows/translate it to ErrorStatus.JWT_INVALID for clearer control flow;
update the method to proceed to the rest of TokenResponse creation only after a
successful validateToken call.
🧹 Nitpick comments (5)
src/main/java/ssurent/ssurentbe/common/status/ErrorStatus.java (1)

12-18: 사용하지 않는 중복 상수 제거

COMM_ERROR_STATUSBAD_REQUEST와 동일한 코드/메시지를 가지고 있으며 코드베이스에서 사용되지 않습니다. 중복 제거를 위해 삭제하는 것을 권장합니다.

src/main/java/ssurent/ssurentbe/domain/users/dto/LoginRequest.java (1)

3-6: 선택적: record의 민감정보 보호를 위한 toString() 오버라이드

LoginRequest는 password를 포함하는 record입니다. 현재 코드에서 요청 객체가 명시적으로 로깅되지는 않으나, Spring 내부 로깅, 모니터링 도구, 또는 예외 스택트레이스에서 기본 toString()이 호출될 가능성이 있습니다. 민감정보 보호를 위해 password를 마스킹하는 것을 권장합니다.

제안 수정
 public record LoginRequest(
         String studentNum,
         String password
 ) {
+    `@Override`
+    public String toString() {
+        return "LoginRequest[studentNum=" + studentNum + ", password=***]";
+    }
 }
src/main/java/ssurent/ssurentbe/common/config/SecurityConfig.java (1)

25-37: CORS 설정이 누락되어 있습니다.

프론트엔드 애플리케이션에서 API를 호출할 경우, CORS(Cross-Origin Resource Sharing) 설정이 없으면 브라우저에서 요청이 차단될 수 있습니다. 프론트엔드와 백엔드가 다른 도메인/포트에서 실행된다면 CORS 설정을 추가하는 것을 고려하세요.

♻️ CORS 설정 추가 예시
 http
+        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
         .csrf(csrf -> csrf.disable())
         .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
         // ... 나머지 설정

별도의 CorsConfigurationSource 빈을 정의하거나, @CrossOrigin 어노테이션을 컨트롤러에 추가할 수 있습니다.

src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java (1)

51-67: 로그인 시 비밀번호 검증 후 isDeleted 체크 순서를 고려하세요.

현재 구현은 비밀번호가 맞더라도 탈퇴한 사용자에게 USER_WITHDRAWN 에러를 반환합니다. 이는 공격자에게 해당 학번으로 탈퇴한 계정이 존재한다는 정보를 노출할 수 있습니다. 보안 강화를 위해 탈퇴 여부와 관계없이 동일한 INVALID_CREDENTIALS 에러를 반환하는 것을 고려해 볼 수 있습니다.

src/main/java/ssurent/ssurentbe/common/jwt/JwtTokenProvider.java (1)

75-93: validateToken 메서드의 반환 타입이 실제 동작과 불일치합니다.

이 메서드는 boolean을 반환하도록 선언되어 있지만, 실제로는 유효한 토큰일 때만 true를 반환하고, 유효하지 않은 토큰일 때는 false 대신 예외를 던집니다. 이로 인해 호출하는 코드에서 if (!validateToken(token))과 같은 불필요한 조건문이 작성될 수 있습니다 (AuthService.refresh 참조).

반환 타입을 void로 변경하거나, 메서드 이름을 validateTokenOrThrow로 변경하여 예외를 던진다는 것을 명확히 하는 것을 권장합니다.

♻️ 수정 제안
-public boolean validateToken(String token) {
+public void validateToken(String token) {
     try {
         Jwts.parser()
                 .verifyWith(secretKey)
                 .build()
                 .parseSignedClaims(token);
-        return true;
     } catch (SignatureException e) {
         throw new GeneralException(ErrorStatus.JWT_INVALID_SIGNATURE);
     } // ... 나머지 catch 블록
 }

Copy link
Contributor

@fprtmjinho fprtmjinho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 뭐가 뭔지 몰라서 보기만했어요

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/main/java/ssurent/ssurentbe/domain/users/enums/PanaltyTypes.java`:
- Around line 3-5: Add `@Enumerated`(EnumType.STRING) to the
UserPanaltyLog.panaltyType field so PanaltyTypes is persisted by name instead of
ordinal; update the PanaltyTypes enum definition (public enum PanaltyTypes) only
if needed to include any legacy constants (e.g., BANNED) to avoid
compile/runtime mismatches. Also add a DB migration that converts existing
ordinal values to their String names (e.g., rows where panalty_type = 2 ->
'BANNED', 1 -> 'UNAUTHORIZED_USE', 0 -> 'OVERDUE') and run it before deploying
the annotation change to prevent data corruption.
🧹 Nitpick comments (1)
src/main/java/ssurent/ssurentbe/common/config/SwaggerConfig.java (1)

18-31: 전역 SecurityRequirement로 인해 공개 엔드포인트도 인증 필요로 표시됩니다.

회원가입/로그인처럼 공개되어야 하는 API까지 Swagger에서 인증 필요로 보일 수 있습니다. 전역 적용을 제거하고, 보안이 필요한 컨트롤러/메서드에만 @SecurityRequirement를 붙이는 방식이 더 정확합니다.

♻️ 제안 변경 (전역 적용 제거)
         return new OpenAPI()
                 .info(new Info()
                         .title("SSURent API")
                         .description("SSURent 백엔드 API 문서")
                         .version("v1.0.0"))
-                .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
                 .components(new Components()
                         .addSecuritySchemes(securitySchemeName,
                                 new SecurityScheme()

@CromNEXT CromNEXT merged commit 9d964df into Develop Feb 3, 2026
1 check passed
@CromNEXT CromNEXT deleted the feat/#3 branch February 3, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 새로운기능구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] : 기본적인 Auth기능 구현

3 participants